package com.wyt.common.file.video;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.function.Consumer;

/**
 * 读取视频流文件并提取一帧一帧的数据，然后通过回调函数处理一帧一帧的数据
 *
 * 视频文件直接提取流定长发送，会造成花屏。
 * 利用javacv一帧一帧提取字节数组发送，提取的帧是解码后的数据，内容量很大，不再属于h264编码，所以发送到前端无法解析。
 * 读取视频文件，根据001、0001手动截取一帧帧视频流，注意001、0001是每一帧的开头，转发时也要发送出去
 * 前端可以解码展示，但是python程序捕获不到帧数据，于是把小的信息帧数据跟后续的帧数据进行合并，然后在获取完整帧，且帧数据大于50和字节后再一起发送
 */
public class H264Parser {

    private static int sleepTime = 40;

    static int cnt =0;

    //核心思想是通过队列缓存startCodeLength个数据，每次读取一个字节数据，保存到队列尾部，
    // 然后判断是否满足001或0001这个要求，满足则为一帧数据直接返回，不满足则移除头部数据并写入到ByteBuffer里面，
    // 如此循环到最后，再处理残留数据即可。
    //算法可以优化，每次读取多一些数据，然后通过KMP算法查找001或0001，然后再复制。
    public static void parseH264(InputStream inputStream, Consumer<ByteBuffer> callback) throws IOException {
        LinkedList<Integer> cacheList = new LinkedList<>();
        ByteBuffer byteBuffer = ByteBuffer.allocate(2 * 1024 * 1024);

        while (true) {
            int value = inputStream.read();
            if (value == -1) break;
            cacheList.addLast(value);
            if (cacheList.size() < 4) {
                continue;
            }
            if (ishead(cacheList)) break;
            else {
                cacheList.removeFirst();
            }
        }
        cacheList.forEach(temp -> byteBuffer.put((byte) temp.intValue()));

        cacheList.clear();

        while (true) {
            int value = inputStream.read();
            if (value == -1) break;
            cacheList.addLast(value);
            if (cacheList.size() < 4) {
                continue;
            }
            if (ishead(cacheList)) {
                if(byteBuffer.position() < 50){
                    cacheList.forEach(temp -> byteBuffer.put((byte) temp.intValue()));
                    cacheList.clear();
                    continue;
                }

                if (callback != null) {
                    callback.accept(byteBuffer);
                }

                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                byteBuffer.clear();
                cacheList.forEach(temp -> byteBuffer.put((byte) temp.intValue()));
                cacheList.clear();
            } else {
                byteBuffer.put((byte) cacheList.get(0).intValue());
                cacheList.removeFirst();
            }
        }

        cacheList.forEach(temp -> byteBuffer.put((byte) temp.intValue()));
        if (callback != null) {
            callback.accept(byteBuffer);
        }
    }

    private static boolean ishead(LinkedList<Integer> list){
        if (list.size() !=4) return false;
        int v1 = list.get(0);
        int v2 = list.get(1);
        int v3 = list.get(2);
        int v4 = list.get(3);
        if((v1 == 0 && v2==0 && v3==1) || (v1 == 0 && v2==0 && v3==0 && v4 ==1))
            return true;
        else return false;
    }

    public static void main(String[] args) {
        try (FileInputStream inputStream = new FileInputStream("D:\\tmp-data\\1694511149969.h264");){
            parseH264(inputStream,buffer -> {
                System.out.println(buffer.position());
                cnt ++;
            });
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        System.out.println(cnt);
    }
}

